/*
 * $Id: ExecResult.java,v 1.4 2009/08/20 20:37:37 navdeep_mahajan Exp $
 * 
 * Copyright (C) 2008 General Electric Company. All Rights Reserved.
 * 
 * This software is the confidential and proprietary information of the General
 * Electric Company (GE). You shall not disclose this software and shall use it
 * only in accordance with the terms of the license agreement you entered into
 * with GE.
 * 
 * GE MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
 * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
 * NON-INFRINGEMENT. GE SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
 * AS A RESULT OF USING, MODIFYING, OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES.
 */
package com.ge.healthcare.autosc.common.util;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Vector;

import com.ge.healthcare.autosc.common.ASCLogger;
import com.ge.healthcare.autosc.common.ASCUtil;
import com.ge.healthcare.autosc.common.exceptions.BasicException;

/**
 * Each ExecResult instance performs a single exec() and captures the results.
 * This class is typically used for running shell and Perl commands.
 * 
 * @author 212042946
 * 
 */
public class ExecResult {
	/** The command being executed. */
	private String command = null;

	/** Environment list for the command being executed. */
	private String[] envList = null;

	/** Exit value from running the command. */
	private int exitValue = 0;

	/** Standard output from the executed command. */
	private String[] stdOutput = null;

	/** Standard error from the executed command. */
	private String[] stdError = null;

	/**
	 * Buffer containing the details of the command in a format suitable for the
	 * text of an error message. If an exception is detected executing the
	 * command or getting its results, this is used as the text of the thrown
	 * exception. The toString() method returns this as a string.
	 */
	private StringBuffer errorBuffer = null;

	/**
	 * Character string used to indicate the separation between lines of text.
	 */
	private String lineSeparator = System.getProperty("line.separator", "\n");

	/**
	 * Indentation for lines of text within the error buffer.
	 */
	private final String indent = "  ";

	/**
	 * Execute a system command, capturing its results.
	 * 
	 * @param myCommand
	 *            The system command to run. The command will be parsed into
	 *            tokens using white-space as delimiters.
	 * 
	 * @param myEnvList
	 *            Environment variables to apply to the command. Each element in
	 *            the array is an environment variable in the form name=value.
	 *            If there are no environment variables to define, use null.
	 * 
	 * @exception AscServiceAbortException
	 *                If an exception is detected executing the command or
	 *                gathering its results. It is the caller's responsibility
	 *                to report an error if an exception is thrown.
	 */
	public ExecResult(String myCommand, String[] myEnvList)
			throws BasicException {
		command = myCommand;
		prepareToExecute(myEnvList);
		Runtime myRuntime = Runtime.getRuntime();
		Process myProcess = null;
		try {
			myProcess = myRuntime.exec(command, envList);
		} catch (Exception e) {
			reportExecutionException(e);
		}
		waitForFinishAndCaptureResults(myProcess);
	}

	/**
	 * Execute a system command, capturing its results.
	 * 
	 * @param myCommandList
	 *            The system command to run. The command is assumed to be
	 *            pre-parsed into the elements of the string.
	 * 
	 * @param myEnvList
	 *            Environment variables to apply to the command. Each element in
	 *            the array is an environment variable in the form name=value.
	 *            If there are no environment variables to define, use null.
	 * 
	 * @exception AscServiceAbortException
	 *                If an exception is detected executing the command or
	 *                gathering its results. It is the caller's responsibility
	 *                to report an error if an exception is thrown.
	 */
	public ExecResult(String[] myCommandList, String[] myEnvList)
			throws BasicException {
		StringBuffer commandBuffer = new StringBuffer();
		for (int idx = 0; idx < myCommandList.length; idx += 1) {
			if (idx != 0) {
				commandBuffer.append(' ');
			}
			commandBuffer.append(myCommandList[idx]);
		}
		command = commandBuffer.toString();
		ASCLogger.info("Executing command: " + command);
		prepareToExecute(myEnvList);
		Runtime myRuntime = Runtime.getRuntime();
		Process myProcess = null;
		try {
			myProcess = myRuntime.exec(myCommandList, envList);
		} catch (Exception e) {
			reportExecutionException(e);
		}
		waitForFinishAndCaptureResults(myProcess);
	}

	/**
	 * Wait for a process to finish and capture its results.
	 * 
	 * @param myProcess
	 *            The executing process.
	 * 
	 * @exception AscServiceAbortException
	 *                If an exception is detected executing the command or
	 *                gathering its results. It is the caller's responsibility
	 *                to report an error if an exception is thrown.
	 */
	private void waitForFinishAndCaptureResults(Process myProcess)
			throws BasicException {
		if (ASCUtil.isNullOrEmptyObject(myProcess)){
			return;
		}
		try {
			exitValue = myProcess.waitFor();
		} catch (Exception e) {
			myProcess.destroy();
			reportExecutionException(e);
		}
		captureResults(myProcess);
	}

	/**
	 * Prepare to execute a system command.
	 * 
	 * @param myEnvList
	 *            Environment variables to apply to the command. Each element in
	 *            the array is an environment variable in the form name=value.
	 *            If there are no environment variables to define, use null.
	 */
	private void prepareToExecute(String[] myEnvList) {
		if (myEnvList == null) {
			envList = new String[0];
		} else {
			envList = myEnvList;
		}
		errorBuffer = new StringBuffer();
		errorBuffer.append("Error executing system command:");
		errorBuffer.append(lineSeparator);
		errorBuffer.append(indent);
		errorBuffer.append(getCommand());
		errorBuffer.append(lineSeparator);
		errorBuffer.append("Environment list:");
		errorBuffer.append(lineSeparator);
		for (int envIdx = 0; envIdx < envList.length; envIdx += 1) {
			errorBuffer.append(indent);
			errorBuffer.append(envList[envIdx]);
			errorBuffer.append(lineSeparator);
		}
	}

	/**
	 * Report an execution exception. This actually just creates an
	 * AscServiceAbortException.
	 * 
	 * @param e
	 *            The exception that was thrown executing the command or waiting
	 *            for it to complete.
	 * 
	 * @exception AscServiceAbortException
	 *                Always throws an exception with the details of the command
	 *                and the error.
	 */
	private void reportExecutionException(Exception e) throws BasicException {
		errorBuffer.append("Exception detected: ");
		errorBuffer.append(e.toString());
		throw new BasicException(errorBuffer.toString());
	}

	/**
	 * Capture the results of executing the command.
	 * 
	 * @param myProcess
	 *            The system process that controlled the execution of the
	 *            command.
	 * 
	 * @exception AscServiceAbortException
	 *                If an exception is detected reading the output stream. It
	 *                is the caller's responsibility to report an error if an
	 *                exception is thrown.
	 */
	private void captureResults(Process myProcess) throws BasicException {
		errorBuffer.append("Return value: ");
		errorBuffer.append(exitValue);
		errorBuffer.append(lineSeparator);
		errorBuffer.append("Standard output:");
		errorBuffer.append(lineSeparator);
		stdOutput = addOutStream(myProcess.getInputStream());
		errorBuffer.append("Standard error:");
		errorBuffer.append(lineSeparator);
		stdError = addOutStream(myProcess.getErrorStream());
	}

	/**
	 * Read the contents of an output stream, adding it to the error buffer and
	 * returning its contents as a string array.
	 * 
	 * @param rawStream
	 *            The output stream from the executed command.
	 * 
	 * @return The contents of the output stream.
	 * 
	 * @exception AscServiceAbortException
	 *                If an exception is detected reading the output stream. It
	 *                is the caller's responsibility to report an error if an
	 *                exception is thrown.
	 */
	private String[] addOutStream(InputStream rawStream) throws BasicException {
		Vector<String> lineList = new Vector<String>();
		InputStreamReader baseReader = new InputStreamReader(
				new BufferedInputStream(rawStream));
		BufferedReader myReader = new BufferedReader(baseReader);
		try {
			for (String currentLine = myReader.readLine(); currentLine != null; currentLine = myReader
					.readLine()) {
				lineList.add(currentLine);
				errorBuffer.append(indent);
				errorBuffer.append(currentLine);
				errorBuffer.append(lineSeparator);
			}
			myReader.close();
		} catch (IOException e) {
			errorBuffer.append("I/O exception detected: ");
			errorBuffer.append(e.toString());
			throw new BasicException(errorBuffer.toString());
		}
		String[] outputList = new String[lineList.size()];
		lineList.copyInto(outputList);
		return outputList;
	}

	/**
	 * Get the command that was executed.
	 * 
	 * @return The command that was executed.
	 */
	public String getCommand() {
		return command;
	}

	/**
	 * Get the list of environment variables applied to the command.
	 * 
	 * @return The list of environment variables applied to the command.
	 */
	public String[] getEnvList() {
		return envList;
	}

	/**
	 * Get the exit value from the executed command.
	 * 
	 * @return The exit value from the executed command.
	 */
	public int getExitValue() {
		return exitValue;
	}

	/**
	 * Get the standard output from the executed command. If the command did not
	 * write to standard output, this is an empty array.
	 * 
	 * @return The standard output from the executed command.
	 */
	public String[] getStdOutput() {
		return stdOutput;
	}

	/**
	 * Get the standard error from the executed command. If the command did not
	 * write to standard error, this is an empty array.
	 * 
	 * @return The standard output from the executed command.
	 */
	public String[] getStdError() {
		return stdError;
	}

	/**
	 * Returns a representation of the system command's execution in a form
	 * suitable for reporting as an error message.
	 * 
	 * @return Command execution report suitable as the text of an error
	 *         message.
	 */
	public String toString() {
		return errorBuffer.toString();
	}
}
